package models

import (
	"errors"
	"strconv"
	"time"
	"math/rand"
	"crypto/sha256"
	"encoding/hex"
	"github.com/dgrijalva/jwt-go"
	"fmt"
	"github.com/astaxie/beego/orm"
)

func SHA(str string) string {

	bytes := []byte(str)

	// Converts string to sha2
	h := sha256.New()                       // new sha256 object
	h.Write(bytes)                          // data is now converted to hex
	code := h.Sum(nil)                      // code is now the hex sum
	codestr := hex.EncodeToString(code)     // converts hex to string

	return codestr
}

var (
	UserList map[string]*User
	TokenList map[string]string
	SecretKey string// A preset secret key that only accessed by you
)

// func init() {
// 	UserList = make(map[string]*User)
// 	u := User{"", "jogy", "kadyxq", Profile{"male", 20, "Singapore", "astaxie@gmail.com"}, "", "", ""}
// 	// UserList["user_11111"] = &u
// 	AddUser(u)
// }

func init() {
	// orm.RegisterDriver("sqlite3", orm.DRSqlite)
	// orm.RegisterDataBase("default", "sqlite3", "hitdic.db")
	// name := "default"
	// force := true
	// verbose := true
	// err := orm.RunSyncdb(name, force, verbose)
	// if err != nil {
	// 	fmt.Println(err)
	// }

	fmt.Printf("Calling user.init")
	orm.RegisterModel(new(User), new(Profile))
}

type User struct {
	Id       string   `orm:"pk"`
	Username string
	Password string
	Profile  *Profile `orm:"rel(one)"` // OneToOne relation
	PasswordHash string
	Salt string
	Token string
	Projects []*HitdicProject   `orm:"reverse(many)"`
}

type Profile struct {
	Id int `orm:"pk;auto"`
	Gender  string
	Age     int
	Address string
	Email   string
	User    *User   `orm:"reverse(one)"` // 设置一对一反向关系(可选)
}

type Token struct {
	Id string // link this with the User Table
	JWT string
}

func AddUser(user User) string {
	fmt.Printf(user.Username)
	o := orm.NewOrm()
	o.Using("default")
	user.Id = "user_" + strconv.FormatInt(time.Now().UnixNano(), 10)
	user.Salt = SHA(string(rand.Intn(10000000)))[12:]
	user.PasswordHash = SHA(user.Salt + user.Password)
	user.Password = ""

	profile := new(Profile)
	
	user.Profile = profile
	
	o.Insert(profile)
	o.Insert(&user)
	
	// UserList[user.Id] = &user
	return user.Id
}

func GetUser(uid string) (*User, error) {
	o := orm.NewOrm()
	user := User{Id: uid}
	err := o.Read(&user)
	
	if err != nil {
		return &User{Id: "-1"}, errors.New("Fail to find the user")
	}
	return &user, nil
	// if u, ok := UserList[uid]; ok {
	// 	return u, nil
	// }
	// return nil, errors.New("User not exists")
}

func GetAllUsers() []*User {
	o := orm.NewOrm()
	var users []*User
	_, err := o.QueryTable("user").All(&users)
	if err != nil {
		return []*User{}
	}
	return users
	// return UserList
}

func UpdateUser(uid string, uu *User) (a *User, err error) {
	if u, ok := UserList[uid]; ok {
		if uu.Username != "" {
			u.Username = uu.Username
		}
		if uu.Password != "" {
			u.Password = uu.Password
		}
		if uu.Profile.Age != 0 {
			u.Profile.Age = uu.Profile.Age
		}
		if uu.Profile.Address != "" {
			u.Profile.Address = uu.Profile.Address
		}
		if uu.Profile.Gender != "" {
			u.Profile.Gender = uu.Profile.Gender
		}
		if uu.Profile.Email != "" {
			u.Profile.Email = uu.Profile.Email
		}
		return u, nil
	}
	return nil, errors.New("User Not Exist")
}

func Login(username, password string) bool {
	for _, u := range UserList {
		if u.Username == username && u.Password == password {
			return true
		}
	}
	return false
}

func GetToken(username, password string) (string, error) {
	o := orm.NewOrm()

	var user User
	err := o.QueryTable("user").Filter("username", username).One(&user)
	if err != nil {
		fmt.Printf("error of reading: ", err)
		return "", errors.New("Fail to find the user")
	}

	hash := SHA(user.Salt + password)
	fmt.Printf("Hash: ", hash)
	if user.PasswordHash == hash {
		claims := &jwt.StandardClaims{
			ExpiresAt: 15000,
			Issuer:    user.Id,
		}
		
		token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
		// ss, err := token.SignedString(SecretKey)
		// fmt.Printf("%v %v", ss, err)

		// token := jwt.New(jwt.GetSigningMethod("HS256"))
		// token.Claims["userid"] = u.Id
		// // Expire in 5 mins
		// token.Claims["exp"] = time.Now().Add(time.Minute * 5).Unix()
		tokenString, err := token.SignedString([]byte(SecretKey))
		if err != nil {
			return "", errors.New("Fail to sign.")
		}
		// TokenList[u.Id] = tokenString
		user.Token = tokenString
		return tokenString, nil
	}

	return "", errors.New("Fail to verify.")
}

func DeleteUser(uid string) {
	// , jwttoken string
	// Apply this operation with the token of the administrator.
	// check the users as administrator with the jwttoken
	delete(UserList, uid)
}

func (t Token)IsExpired() {
	token, err := jwt.Parse(t.JWT, func(token *jwt.Token) (interface{}, error) {
		// Don't forget to validate the alg is what you expect:
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
		}
	
		// hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key")
		return []byte(SecretKey), nil
	})
	
	if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
		fmt.Println(claims["foo"], claims["nbf"])
	} else {
		fmt.Println(err)
	}
}